Session bootstrap: a probe that never ran, and a venv fix that killed every uv tool - #354
Merged
Merged
Conversation
…m that stopped firing Two halves of the same defect, both measured in a fresh container today. The probe: `--check` asks whether the interpreter that will run the suite can import pytest, PyYAML and xdist. It said `import importlib`, which does not bind `importlib.util`, so the snippet raised AttributeError on every real CPython, the `&&` short-circuited, and the OK line printed. This session's first `--check` reported `python3: 3.12 OK` and `pytest: 3.12 OK` beside a `python3 -m pytest` answering `No module named pytest` and a `pytest -n auto` dying on `unrecognized arguments: -n`. The test that was meant to cover exactly this passed throughout: it drove a shell script standing in for python, which answered the fixture's line and could not disagree with the snippet. So the probe is now one string ($IMPORT_PROBE) the suite lifts out and runs on a real interpreter, and a probe that cannot run is reported as a failure rather than falling through to OK — "could not ask" and "asked, nothing missing" were indistinguishable, which is the shape this series keeps finding. The advice: AGENTS.md told a session to bootstrap *if* pytest misbehaved. The container now ships 3.12 with pytest, mypy, ruff and flake8 on it, so the symptom no longer appears and the remaining failure reads like a bad flag. A remedy keyed to a symptom is only as good as the symptom, so the instruction is now unconditional: bootstrap before the first test command. ~10s cold, ~1s warm. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016c1SLkLDW6aEZCVf95XXeu
Pass 1 fixed the source of the ancestry lie — the hook unshallows — but nothing a session reads says so, and `--check` is the only thing that reports the state. This session arrived with 78 of PyAutoMind's 4478 commits, so `--is-ancestor` would have answered "not an ancestor" for merged branches: the answer the ship and close-out procedures act on. Recorded beside the bootstrap instruction, since the bootstrap is the fix. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016c1SLkLDW6aEZCVf95XXeu
Measured this session, after a clean bootstrap: mypy, flake8, black, poetry and pyright all died with ModuleNotFoundError naming themselves, with the package installed two directories away. `uv tool list` said "Failed find package flake8 in tool environment". `--check` reported every one of them `3.12 OK`. Cause, and it is our own fix's blast radius. uv creates each tool env with `bin/python` as a symlink to whatever `python3` was at install time — /usr/local/bin/python3 — and the hook then repoints that path at the session venv. Crucially it repoints it with a WRAPPER SCRIPT, because an earlier pass proved a symlink there loses the venv; the wrapper `exec`s the venv's python, which replaces argv, so CPython never sees the tool env beside it and resolves sys.prefix to the venv. The tool's own site-packages is then off sys.path. ruff survived (native binary) and pytest survived (shimmed straight at the venv). Everything else a session lints with was dead. So a remote session has been linting clean by not linting at all, while CI runs the same tools for real — the local-clean/CI-red generator this series exists to kill, one layer down from where it was last found. Two changes: - `repair_uv_tools` (also `--repair-uv-tools`) repoints any tool env whose interpreter does not resolve to its own prefix at the base 3.12 interpreter, and verifies the repoint rather than assuming it. It asks sys.prefix rather than tracing the link, because the wrapper is invisible to readlink. - `--check` now runs each tool. Version was necessary and never sufficient: these tools reached a 3.12 interpreter, just not theirs. The test that reproduces it needs the wrapper, not a bare symlink — a tool env whose python merely symlinks elsewhere still finds its own pyvenv.cfg and is fine. That is why the breakage arrived with the fix that introduced the wrapper. Left in scripts/ rather than promoted into policy/session_start_hook.sh: the hook is generated into all four organs and two of them cannot be attached to this session, so changing it would make firewall_gate red on repos this session cannot fix. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016c1SLkLDW6aEZCVf95XXeu
…t miss it The follow-up pass 3 named and deliberately did not take. Three passes in a row found a repo whose copy of this guidance had not learned what the previous pass measured, and one found two organs still shipping a bug a third had fixed. Pass 3's reason for deferring was that the per-repo halves differ: each copy named its own test count, its own timings, its own declared deps. Those are exactly what rots — a number generated into every repo's always-loaded context is a confident wrong answer in the other three the moment a suite grows — so they are removed rather than encoded, and a test pins their absence. A repo's own dependencies stay in its .claude/session-python.txt, where the hook reads them at run time. Same shape as the never-rewrite-history policy: one source (policy/remote_sessions.md), N generated copies, a drift check. Opt-in by markers, so a repo this session cannot attach is skipped rather than failing a CI leg on its behalf — PyAutoHeart and PyAutoHands still carry their own text and are a marker addition away, which is the honest state of the rollout. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016c1SLkLDW6aEZCVf95XXeu
Both are rollouts that stop at the repos a session can attach. add_repo was refused for PyAutoHeart and PyAutoHands here, so: their opt-in to the generated remote-session block, and promoting repair_uv_tools from the bootstrap into the hook (where a single-repo session, which never calls the bootstrap, would get it) both need a four-organ session. Dashboard regenerated in the same commit. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016c1SLkLDW6aEZCVf95XXeu
…seam The prompt's Phase 0 is a four-curl probe, and it now has an answer rather than a plan to get one: $GH_TOKEN reaches /user and /rate_limit (200) and every repo-scoped path with 403 "GitHub access is not enabled for this session", unchanged from 2026-08-26. Option 2 needs an org-admin action that has not happened; option 3 rides the same credential. That selects option 1 by the prompt's own rule. Written down so the next session starts at the design rather than at the probe. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016c1SLkLDW6aEZCVf95XXeu
`pyauto-brain feature` scored the parent large (9) and returned split-into-phases. Phase 1 builds the injection seam and proves it on the seven overnight rows; phase 2 ports the remaining four legs, whose real question — whether each script grows its own seam — is answered in phase 1's contract. The parent keeps the design, the measurements and the probe evidence, and is marked phased so the dashboard stops offering it as pickable work. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016c1SLkLDW6aEZCVf95XXeu
Phase 1 of the board's gh-less work is issued: PyAutoBrain#303, prompt advanced draft/ → active/, registered in active.md with no worktree claimed (this is a remote session, so /start_library runs wherever the task is picked up). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016c1SLkLDW6aEZCVf95XXeu
…w-review-tyv13y # Conflicts: # dashboard.html # dashboard.md
Jammy2211
pushed a commit
that referenced
this pull request
Aug 27, 2026
…dispatchable Both close-out PRs this session (#354, #357) showed zero check runs at open, on diffs matching three path filters plus spawn_drift.yml, which has none. The 2026-08-26 record logged this as a one-off GitHub-side miss; at two occurrences it belongs in the record as a repeatable condition, with what actually works. The part that is not obvious and cost a detour: of the four workflows, only firewall_gate.yml can be dispatched to verify a branch. lifecycle_drift.yml and dashboard_refresh.yml self-heal main on a manual dispatch — they reset to origin/main and push — so dispatching them says nothing about the branch and writes to main. Their commands run locally instead. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016c1SLkLDW6aEZCVf95XXeu
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
The session/environment half of the 2026-08-27 mobile-workflow pass. Merge after PyAutoLabs/PyAutoBrain#304:
firewall_gate.ymlpins PyAutoBrain tomainand is path-filtered toscripts/repos_sync.py, which this PR changes.Everything here was measured in this container, not recalled.
The import probe never ran
--checkasks whether the interpreter that will run the suite can import pytest, PyYAML and xdist. It saidimport importlib, which does not bindimportlib.util, so the snippet raisedAttributeErroron every real CPython, the&&short-circuited, and the OK line printed. This session's first--check:beside a
python3 -m pytestansweringNo module named pytestand apytest -n autodying onunrecognized arguments: -n.The test written to cover exactly this passed throughout: it drove a shell script standing in for python, which answered the fixture's line and could not disagree with the snippet. So the probe is now one string (
$IMPORT_PROBE) the suite lifts out and runs on a real interpreter, and a probe that cannot run reports as a failure instead of falling through to OK.The venv fix broke every uv tool, and
--checkcalled them OKAfter a clean bootstrap,
mypy,flake8,black,poetryandpyrightall died withModuleNotFoundErrornaming themselves, packages installed two directories away;uv tool listsaidFailed find package flake8 in tool environment.uv creates each tool env with
bin/pythonsymlinked to whateverpython3was at install time —/usr/local/bin/python3— and the hook replaces that path with a wrapper thatexecs the session venv (a wrapper precisely because an earlier pass proved a symlink there loses the venv). The exec replaces argv, so CPython never sees thepyvenv.cfgbeside it and resolvessys.prefixto the venv; the tool's own site-packages is offsys.path.ruffsurvived (native binary),pytestsurvived (shimmed straight at the venv). Everything else a session lints with was dead — so a remote session has been linting clean by not linting, while CI runs the same tools for real.repair_uv_toolsrepoints any tool env whose interpreter does not resolve to its own prefix, verifying the repoint rather than assuming it, and askingsys.prefixrather than tracing the link (the wrapper is invisible toreadlink).--checknow runs each tool: version was necessary and never sufficient.The advice was keyed to a symptom that stopped firing
AGENTS.md said to bootstrap if pytest misbehaved. The container now ships 3.12 with the linters on it, so the session looks right and the remaining failure reads like a bad flag. Now unconditional, and it names the leg with teeth: the bootstrap unshallows the clones, without which
merge-base --is-ancestorreports merged branches as unmerged.The block is generated now
policy/remote_sessions.mdis the single source, written into each repo'sAGENTS.mdbyrepos_sync.py --writewith a drift check — the follow-up the previous pass named and deferred, because the per-repo halves (test counts, timings, deps) differed. Those are removed rather than encoded, and a test pins their absence: a number generated into every repo's always-loaded context is a confident wrong answer in the others the moment a suite grows. Opt-in by markers, so PyAutoHeart and PyAutoHands — which this session could not attach — are skipped rather than failing a CI leg on their behalf; filed asdraft/maintenance/organs/session_fixes_reach_only_two_organs.md.Also
The board's gh-less prompt had its Phase 0 probe run (
$GH_TOKEN: 200 on/user, 403 on every repo path), which selects the injection seam; the parent is phased and phase 1 is issued as PyAutoLabs/PyAutoBrain#303.Validation
252 tests green (
-n auto),lifecycle.py checkandrepos_sync.py --checkclean,ruffclean on the files touched. Every new guard was confirmed to FAIL against the pre-fix tree first — the uv-tool one needs the wrapper, not a bare symlink, which is why the breakage arrived with the fix that introduced the wrapper.Generated by Claude Code